authguidance.com

JWT Access Token Validation – OAuth Architecture Guidance

Background

Previously we drilled into API Coding Key Points for the initial code sample. Next we will describe some details and technical choices when validating JWTs in APIs.

Common Requirements

When designing token validation in APIs there are a few factors to consider:

Factor Description
Simple Code Validating a JWT should not require a great amount of code or complicate your API
Understanding You should understand how the validation works, so that you can ensure it is doing the right things
Extensible JWT validation should fit with a wider plan to ensure that APIs have what they need to authorize requests
Security Capabilities Some libraries have better capabilities than others, such as the ability to use financial-grade algorithms
Useful Errors You must ensure that API clients receive useful error responses when access tokens fail validation

Libraries v Frameworks

Some technology stacks provide a ‘Resource Server Framework’ that operates like a black box and requires very little code. This is fine when all you need is the default behaviour, but this blog uses a library approach for better control over the above behaviour.

JOSE Libraries

This blog will provide APIs developed in the following languages and in each case will use a JOSE library, so that the most leading edge security options are available, in case ever needed in future:

JOSE libraries support a number of OAuth related security specifications:

JWT Validation Code

The JWT validation code in our initial API required very little code, and we will explain the key behaviour in the following sections:

Viewing JWT Access Tokens

JWT access tokens are issued by the Authorization Server and consist of three parts. Cognito uses an asymmetric private key to create the digital signature:

Here is an example AWS Cognito JWT access token:

eyJraWQiOiIyV01TWGcwekEydVFlTjE0ZWlma0o5Nk5TTURpUmdtSXNGcE9yNHNJVWRvPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJhNmI0MDRiMS05OGFmLTQxYTItOGU3Zi1lNDA2MWRjMGJmODYiLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb21cL2V1LXdlc3QtMl9xcUpnVmV1VG4iLCJ2ZXJzaW9uIjoyLCJjbGllbnRfaWQiOiI2dGcwcWdsZGRwdnFoNzRrM2piZjFtbWo2NCIsIm9yaWdpbl9qdGkiOiI0NDNmNGMzNS1iNmRiLTQzYzktODgzYS0wMThmOWM5NzMzOGMiLCJldmVudF9pZCI6ImMyMGViMGI1LWJhODQtNDMzNC04M2NhLWI3NWExMGQ1ZmY0NyIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUiLCJhdXRoX3RpbWUiOjE2MzQ5NzgwNDQsImV4cCI6MTYzNDk4MTY0NCwiaWF0IjoxNjM0OTc4MDQ0LCJqdGkiOiI3Zjk0YzJhNC0zNWE3LTQ2NGItODFkMC05MDQ4YmUzODBhODkiLCJ1c2VybmFtZSI6ImE2YjQwNGIxLTk4YWYtNDFhMi04ZTdmLWU0MDYxZGMwYmY4NiJ9.CM5j3AXOGNL77AjW1-QImb6uwR8JE6ZojWOfKI-nZJhkCsDlSmG2qMpq6Ntkm-Pve6zA9TkWbCWSA1MHKwgQPMXobz5UDQSJSGwiEIa4L9Q6eCGIEDs5153DkXRD4KLYu-SGLOgSurzRuc-EUINDA7zyErNDKGbaFf8qPV5QuMTCQGO-h1SkvLU85yc8Xp6Q8MYv9ydf1oWukjCJdDSzlUdjP6Vsb3V5xKaTBWFvHpwoo5cwyD51Pu8Lsu7p7B-vQAfzXjfgPjnc5EQY_fNYZoh9MaB6b3EnGgZz0oY9gCZHhlr_cRxgZlR_-J9KeUIYcW5Mna-J5GYFe6eRcEePxw

We can paste this into an Online JWT Viewer, to view the details, and note the Key Identifier (kid) field in the JWT header:

API Validation Steps

APIs must validate JWT access tokens on every request, which is designed to be a fast and scalable operation. The API must provide correct inputs to the security library in order for this to be done correctly:

Check Description
Algorithm The API specifies one or more algorithms that can be used, and AWS Cognito tokens use the mainstream RS256 option
Issuer The API expects the issuer in the token to be the value from OpenID Connect metadata
Audience The audience represents a set of related APIs, and the API must specify a value such as api.mycompany.com
Time Access tokens should be short lived, such as 30 minutes, and the API must check the token is valid for use and not expired
Public Key The API provides the token signing public key to the library, and the most common way to do this is via a trusted download URL

API OAuth Configuration

The initial API reflected the above settings in its configuration file. Note that AWS Cognito does not support an audience claim for access tokens so this was left blank.

Failed Token Validation

When any token validation checks fail, the API should return an error response with a 401 status code. I also like to return a clear payload as follows:

"code": "unauthorized",
"message": "Missing, invalid or expired access token"

In production systems this type of error is expected to occur frequently, since UI clients use short lived access tokens. When a client receives this response, a token refresh operation will be performed.

JWT Algorithms

An attacker could send an untrusted JWT with ‘alg: none’ in the JWT header, to bypass cryptographic security, but this cannot happen if you provide only secure algorithms to the security library.

Token Signing Public Keys

The API provides the security library with a trusted JWKS Endpoint and that for my AWS Cognito endpoint is at the below address:

This endpoint returns multiple public keys in a JSON Web Keyset and each of these has a key identifier. The entry whose kid value matches that in the access token’s JWT header is used to verify the token:

JWT Signature Verification

The above JWT Viewer website allows us to manually paste in a JSON Web Key. If we paste in a value from the JWKS endpoint whose kid does not match that in the JWT header we get a verification failure:

This result will also be returned if an attacker manually creates a JWT and sends it to the API, or if the JWT is tampered with and its contents altered.

JSON Web Keyset Caching

Any good JWT library will also cache the JSON Web Keyset details, with the following behaviour on subsequent requests:

Check Description
Same kid The cached JSON Web Key is used, to prevent the need for further calls to the Authorization Server’s JWKS endpoint
New kid A new call to the JWKS Endpoint is used, to get updated JSON Web Keys that include the new key identifier

Token Signing Key Rotation

The Authorization Server will rotate its cryptographic keys occasionally, which will result in a new private key being used to sign JWTs and new public keys being made available for verification.

For a while the JWKS Endpoint will then return both old and new keys, until the old one is retired. A good API security library will cache JWKs correctly, so that the API automatically and reliably copes with renewal.

API Authorization

Once the JWT processing is done, the claims in the JWT payload can be trusted by the API and used to authorize requests for API data. We will discuss use of claims in further detail in this blog’s Authorization Design.

Where Are We?

We covered the key behaviour and design choices when validating JWTs in APIs. Next we will make some recommendations on getting started with your own Authorization Server.

Next Steps